package com.sny.strategy.template;

import com.sny.constants.FileConstants;
import com.sny.dto.FileStorageDto;
import com.sny.dto.FileUploadDTO;
import com.sny.service.IFileStorageService;
import com.sny.strategy.SliceUploadStrategy;
import com.sny.util.*;
import com.sny.vo.param.FileUploadRequestParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Slf4j
public abstract class SliceUploadTemplate implements SliceUploadStrategy {
    @Autowired
    private IFileStorageService fileInfoService;

    @Autowired
    private FilePathUtil filePathUtil;

    public abstract boolean upload(FileUploadRequestParam param);

    protected File createTmpFile(FileUploadRequestParam param) {

        FilePathUtil filePathUtil = SpringContextHolder.getBean(FilePathUtil.class);
        String fileName = param.getFile().getOriginalFilename();
        String uploadDirPath = filePathUtil.getPath(param);
        String tmpFilePath = filePathUtil.getTmpFilePath(param);
        File tmpDir = new File(uploadDirPath);
        File tmpFile = new File(tmpFilePath);
        if (!tmpDir.exists()) {
            tmpDir.mkdirs();
        }
        return tmpFile;
    }

    @Override
    public FileUploadDTO sliceUpload(FileUploadRequestParam param) {

        boolean isOk = this.upload(param);
        if (isOk) {
            File tmpFile = this.createTmpFile(param);
            FileUploadDTO fileUploadDTO = this.saveAndFileUploadDTO(param, tmpFile);
            return fileUploadDTO;
        }
        String md5 = FileMD5Util.getFileMD5(param.getFile());

        Map<Integer, String> map = new HashMap<>();
        map.put(param.getChunk(), md5);
        return FileUploadDTO.builder().chunkMd5Info(map).build();
    }

    /**
     * 检查并修改文件上传进度
     */
    public boolean checkAndSetUploadProgress(FileUploadRequestParam param, String uploadDirPath) {

//        String fileName = param.getFile().getOriginalFilename();
        FilePathUtil filePathUtil = SpringContextHolder.getBean(FilePathUtil.class);
        String fileName = param.getName();
        String confFilePath = filePathUtil.getConfPath(param);
        File confFile = new File(confFilePath);
        byte isComplete = 0;
        RandomAccessFile accessConfFile = null;
        try {
            accessConfFile = new RandomAccessFile(confFile, "rw");
            //把该分段标记为 true 表示完成
            log.info("set part " + param.getChunk() + " complete");
            //创建conf文件文件长度为总分片数，每上传一个分块即向conf文件中写入一个127，那么没上传的位置就是默认0,已上传的就是Byte.MAX_VALUE 127
            accessConfFile.setLength(param.getChunks());
            accessConfFile.seek(param.getChunk());
            accessConfFile.write(Byte.MAX_VALUE);

            //completeList 检查是否全部完成,如果数组里是否全部都是127(全部分片都成功上传)
            byte[] completeList = FileUtils.readFileToByteArray(confFile);
            isComplete = Byte.MAX_VALUE;
            for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
                //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
                isComplete = (byte) (isComplete & completeList[i]);
                log.info("check part " + i + " complete?:" + completeList[i]);
            }

        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            FileUtil.close(accessConfFile);
        }
        boolean isOk = setUploadProgress2Redis(param, confFilePath, fileName, confFile, isComplete);
        return isOk;
    }

    /**
     * 把上传进度信息存进redis
     */
    private boolean setUploadProgress2Redis(FileUploadRequestParam param, String confFilePath,
                                            String fileName, File confFile, byte isComplete) {

        RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class);
        if (isComplete == Byte.MAX_VALUE) {
            redisUtil.hset(FileConstants.FILE_UPLOAD_STATUS + "_" + param.getAppId(), param.getMd5(), "true");
            redisUtil.del(FileConstants.FILE_MD5_KEY + param.getAppId() + "_" + param.getMd5());
            confFile.delete();
            return true;
        } else {
            if (!redisUtil.hHasKey(FileConstants.FILE_UPLOAD_STATUS + "_" + param.getAppId(), param.getMd5())) {
                redisUtil.hset(FileConstants.FILE_UPLOAD_STATUS + "_" + param.getAppId(), param.getMd5(), "false");
                redisUtil.set(FileConstants.FILE_MD5_KEY + param.getAppId() + "_" + param.getMd5(), confFilePath);
            }

            return false;
        }
    }

    /**
     * 保存文件操作
     */
    public FileUploadDTO saveAndFileUploadDTO(FileUploadRequestParam param, File tmpFile) {
        String fileName = param.getFile().getOriginalFilename();
        FileUploadDTO fileUploadDTO = null;
        try {
            //创建目录
            String fileId = UUID.randomUUID().toString();
            String filePath = filePathUtil.getPathFileId(fileId);
            File targetFile = new File(filePath);
            if (!targetFile.exists()) {
                targetFile.mkdirs();
            }
            //目录创建完毕，移动文件
            String fileExt = FileUtil.getExtension(param.getFile().getOriginalFilename());
            fileUploadDTO = moveFile(tmpFile, fileId, fileExt);
            if (fileUploadDTO.isUploadComplete()) {
                log.info("upload complete !!" + fileUploadDTO.isUploadComplete() + " name=" + fileName);
                //保存文件信息到数据库
                FileStorageDto fileInfo = new FileStorageDto();
                fileInfo.setMD5(param.getMd5());
                fileInfo.setName(fileId);
                fileInfo.setSize(fileUploadDTO.getSize());
                fileInfo.setSuffix(fileUploadDTO.getFileExt());
                fileInfo.setAppId(param.getAppId());
                fileInfo.setFilename(param.getName());
                fileInfo.setFileId(fileId);
                fileInfo.setStorageDir(FileConstants.FILE_SEPARATORCHAR + fileId);
                fileInfo.setUrl(FileConstants.FILE_SEPARATORCHAR + fileId + FileConstants.FILE_SEPARATORCHAR);
                fileInfoService.save(fileInfo);
            }

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {

        }
        return fileUploadDTO;
    }

    /**
     * 文件重命名
     *
     * @param toBeRenamed   将要修改名字的文件
     * @param toFileNewName 新的名字
     */
    private FileUploadDTO renameFile(File toBeRenamed, String toFileNewName) {
        //检查要重命名的文件是否存在，是否是文件
        FileUploadDTO fileUploadDTO = new FileUploadDTO();
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            log.info("File does not exist: {}", toBeRenamed.getName());
            fileUploadDTO.setUploadComplete(false);
            return fileUploadDTO;
        }
        String ext = FileUtil.getExtension(toFileNewName);
        String p = toBeRenamed.getParent();
        String filePath = p + FileConstants.FILE_SEPARATORCHAR + toFileNewName;
        File newFile = new File(filePath);
        //修改文件名
        boolean uploadFlag = toBeRenamed.renameTo(newFile);

        fileUploadDTO.setMtime(DateUtil.getCurrentTimeStamp());
        fileUploadDTO.setUploadComplete(uploadFlag);
        fileUploadDTO.setPath(filePath);
        fileUploadDTO.setSize(newFile.length());
        fileUploadDTO.setFileExt(ext);
        fileUploadDTO.setFileId(toFileNewName);

        return fileUploadDTO;
    }

    /**
     * 文件重命名
     *
     * @param sourceFile 将要移动的文件
     * @param fileId     文件id
     */
    private FileUploadDTO moveFile(File sourceFile, String fileId, String fileExt) {
        //检查要重命名的文件是否存在，是否是文件
        FileUploadDTO fileUploadDTO = new FileUploadDTO();
        if (!sourceFile.exists() || sourceFile.isDirectory()) {
            log.info("File does not exist: {}", sourceFile.getName());
            fileUploadDTO.setUploadComplete(false);
            return fileUploadDTO;
        }
        String oldPath = sourceFile.getParent();
        log.info("oldPath:{}", oldPath);
        String filePath = filePathUtil.getPathFileId(fileId);
        String path = filePath + FileConstants.FILE_SEPARATORCHAR + fileId + "." + fileExt;
        File newFile = new File(path);
        //移动文件
        boolean uploadFlag = sourceFile.renameTo(newFile);
        log.info("uploadFlag:{}", uploadFlag);
        //删除原文件目录
        boolean deletFlag = new File(oldPath).delete();
        log.info("deletFlag:{}", deletFlag);

        fileUploadDTO.setMtime(DateUtil.getCurrentTimeStamp());
        fileUploadDTO.setUploadComplete(uploadFlag);
        fileUploadDTO.setPath(path);
        fileUploadDTO.setSize(newFile.length());
        fileUploadDTO.setFileExt(fileExt);
        fileUploadDTO.setFileId(fileId);
        return fileUploadDTO;
    }

}
